package channelService

import (
	".go-fox-edge-channel-s7-client/channelEntity"
	".go-fox-edge-channel-s7-client/channelEvent"
	"encoding/json"
	"gitee.com/fierce_wolf/go-fox-edge-channel-common/channelStatus"
	"gitee.com/fierce_wolf/go-fox-edge-common/commLogger"
	"gitee.com/fierce_wolf/go-fox-edge-common/commUtil/Json"
	"gitee.com/fierce_wolf/gos7"
	"net"
	"reflect"
	"strconv"
	"sync"
	"time"
)

type S7Connector struct {
	channelName2Client  sync.Map
	channelName2Handler sync.Map
}

func (e *S7Connector) Scheduler() {
	go e.connectPLC()
	go e.invalidPLC()
	go e.inspectPLC()
}

func (e *S7Connector) connectPLC() {
	for {
		channelName2Entity := service.getChannelNames()
		for channelName := range channelName2Entity {
			val, ok := service.channelName2Entity.Load(channelName)
			if !ok {
				continue
			}
			entity := val.(*channelEntity.S7ClientEntity)

			// 检查：连接是否已经建立
			if e.hasConnect(entity.ChannelName) {
				continue
			}

			// 建立连接
			err := e.createConnect(entity)
			if err != nil {
				continue
			}

			channelStatus.UpdateParamStatus(entity.ChannelName, "connected", true)

			commLogger.Info("建立连接：" + entity.ChannelName + ", 连接信息->IP=" + entity.IP + ",Rack=" + strconv.Itoa(entity.Rack) + ",Slot=" + strconv.Itoa(entity.Slot))
		}

		time.Sleep(1 * time.Second)
	}
}

func (e *S7Connector) invalidPLC() {
	for {
		jsn := channelEvent.Invalid.Wait(1000)
		if jsn == "" {
			continue
		}

		entity := &channelEntity.S7ClientEntity{}
		err := json.Unmarshal([]byte(jsn), entity)
		if err != nil {
			continue
		}

		// 删除失效的连接
		e.deleteHandler(entity)
	}
}

func (e *S7Connector) inspectPLC() {
	for {
		channelName2Entity := service.getChannelNames()
		for channelName := range channelName2Entity {
			val, ok := service.channelName2Entity.Load(channelName)
			if !ok {
				continue
			}
			entity := val.(*channelEntity.S7ClientEntity)

			// 检查：连接是否已经建立
			val, ok = e.channelName2Client.Load(entity.ChannelName)
			if !ok {
				continue
			}
			client := val.(gos7.Client)

			// 检查：状态是否出错
			_, err := client.PLCGetStatus()
			if err == nil {
				continue
			}

			// 检查：是否为指定的网络异常
			if !e.isOpError(err) {
				continue
			}

			commLogger.Info("连接异常：" + entity.ChannelName + ", 连接信息->IP=" + entity.IP + ",Rack=" + strconv.Itoa(entity.Rack) + ",Slot=" + strconv.Itoa(entity.Slot))

			// 发出通知：失效掉该异常的连接
			channelEvent.Invalid.Notify(Json.BuildJson(entity))
		}

		time.Sleep(10 * time.Second)
	}
}

func (e *S7Connector) isOpError(err error) bool {
	return reflect.TypeOf(err).Kind() == reflect.TypeOf(&net.OpError{}).Kind()
}

func (e *S7Connector) hasConnect(channelName string) bool {
	// 检查：连接是否已经建立
	_, ok := e.channelName2Client.Load(channelName)
	if !ok {
		return false
	}
	_, ok = e.channelName2Handler.Load(channelName)
	if !ok {
		return false
	}

	return true
}

func (e *S7Connector) createConnect(entity *channelEntity.S7ClientEntity) error {
	// 释放掉可能存在的旧连续信息
	e.channelName2Client.Delete(entity.ChannelName)
	val, ok := e.channelName2Handler.Load(entity.ChannelName)
	if ok {
		handler := val.(*gos7.TCPClientHandler)
		handler.Close()

		e.channelName2Handler.Delete(entity.ChannelName)
	}

	//PLC tcp连接客户端
	handler := gos7.NewTCPClientHandler(entity.IP, entity.Rack, entity.Slot)
	//连接及读取超时
	handler.Timeout = 200 * time.Second
	//关闭连接超时
	handler.IdleTimeout = 200 * time.Second
	//打开连接
	err := handler.Connect()
	if err != nil {
		handler.Close()
		return err
	}

	//获取PLC对象
	client := gos7.NewClient(handler)

	// 保存连接信息
	e.channelName2Client.Store(entity.ChannelName, client)
	e.channelName2Handler.Store(entity.ChannelName, handler)

	return nil
}

func (e *S7Connector) deleteHandler(entity *channelEntity.S7ClientEntity) error {
	// 释放掉可能存在的旧连续信息
	e.channelName2Client.Delete(entity.ChannelName)
	val, ok := e.channelName2Handler.Load(entity.ChannelName)
	if ok {
		handler := val.(*gos7.TCPClientHandler)
		handler.Close()

		e.channelName2Handler.Delete(entity.ChannelName)
	}

	channelStatus.UpdateParamStatus(entity.ChannelName, "connected", false)

	commLogger.Info("连接断开：" + entity.ChannelName + ", 连接信息->IP=" + entity.IP + ",Rack=" + strconv.Itoa(entity.Rack) + ",Slot=" + strconv.Itoa(entity.Slot))

	return nil
}
